const http = require('http');
const fs = require('fs').promises;
const path = require('path');
const url = require('url');
const { createReadStream, createWriteStream, readFileSync } = require('fs');

// 第三方模块
const ejs = require('ejs'); // 模板引擎
const mime = require('mime'); // 文件类型
const chalk = require('chalk'); // 画颜色

class Server {
  constructor(options) {
    // console.log(options);
    this.port = options.port;
    this.directory = options.directory;
    this.template = readFileSync(
      path.resolve(__dirname, 'render.html'),
      'utf8'
    );
  }
  async handleServer(req, res) {
    let { pathname } = url.parse(req.url, true);
    pathname = decodeURIComponent(pathname); // 防止路径中有中文
    // console.log(pathname);
    let filePath = path.join(this.directory, pathname);
    try {
      let staeObj = await fs.stat(filePath);
      if (staeObj.isFile()) {
        this.sendFile(req, res, staeObj, filePath);
      } else {
        // 需要列出文件中的内容 fs-extra（封装了fs，使用promise）

        let dirs = await fs.readdir(filePath);
        // 文件访问的路径采用绝对路径
        dirs = dirs.map((item) => {
          return {
            dir: item,
            href: path.join(pathname, item),
          };
        });
        let result = await ejs.render(this.template, { dirs }, { async: true });
        // console.log(result);
        res.setHeader('Content-Type', 'text/html;charset=utf-8');
        res.end(result);
      }
    } catch (error) {
      console.log(error);
      this.sendError(req, res, error);
    }
  }
  sendFile(req, res, statObj, filePath) {
    // 看文件是否变化，协商缓存，是包含首次访问的资源（包含首页index.html）
    if (this.cache(req, res, statObj, filePath)) {
      res.statusCode = 304;
      res.end();
      return;
    }

    res.setHeader('Content-Type', mime.getType(filePath) + ';charset=utf-8');
    createReadStream(filePath).pipe(res);
  }
  cache(req, res, statObj, filePath) {
    // 设置缓存， 默认强制缓存10s内不向服务器发起请求（首页不会被缓存），引用的资源可以被缓存
    res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toGMTString());
    /**
     * no-cache: 表示每次像服务器发请求
     * no-store: 没有这个缓存，浏览器不进行缓存
     */
    // res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Cache-Control', 'max-age=10');

    // 过了10s了，文件还是没有变化，可以不用返回文件，告诉浏览器在缓存里面找就好，这就是协商缓存
    // 协商缓存，返回304给浏览器，表示找缓存即可
    /**
     *  默认先走强制缓存，10s内不会发生服务器请求，10s后会再次发生请求，后端要进行对比
     * 1. 文件没有变化，直接返回304，浏览器会在缓存中找，之后的10s还是会在缓存找
     * 2. 文件变化了，放回最新的文件，10s后还是走缓存，不停的循环
     */

    // 1.根据最后修改时间来判断文件是否修改了  304 服务端设置
    const ifModifiedSince = req.headers['if-modified-since'];
    const ctime = statObj.ctime.toGMTString();
    // statObj.ctime; // 文件修改时间
    // Last-Nodified的缺陷：如果文件没变（改了文件，但又改回去，最终没有变化），但修改那一瞬间修改时间变了
    res.setHeader('Last-Nodified', ctime);

    //
    if (ifModifiedSince !== ctime) {
      // 如果前端传递过来的最后修改时间和我的ctime时间一样，说明这个文件没有被修改过
      // 没有缓存
      return false;
    }

    /**
     * 采用指纹Etag：根据文件产生一个唯一的标识（md5）
     */

    // 相等就有缓存
    return true;
  }
  sendError(req, res, error) {
    res.statusCode = 404;
    res.end('not found 404');
  }
  start() {
    // console.log('start');
    const server = http.createServer(this.handleServer.bind(this));
    server.listen(this.port, () => {
      console.log(
        chalk.yellow(
          'starting up laiht-server ' +
            path.relative(process.cwd(), this.directory) || './'
        )
      );
      console.log(chalk.green(' http://localhost:' + this.port));
    });
  }
}
module.exports = Server;
